﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Red.GameData.Raw
{
    /// <summary>
    /// Option in the enumeration
    /// Usually a name-value pair
    /// NOTE: all enumeration values are assumed to be integers
    /// </summary>
    public class ReflectionEnumOption
    {
        /// <summary>
        /// Name for the enumeration value - this is how the value is stored in the XMLs
        /// If no display name is specified this is also used in all of the user dialog boxes
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Get editor comment (user hint) for this enumeration value.
        /// </summary>
        public string Comment { get; private set; }

        /// <summary>
        /// Initialize the enum option
        /// NOTE: values are not needed for anything yet and thus they are not stored
        /// </summary>
        /// <param name="name">Name of the option</param>
        /// <param name="comment">User hint</param>
        public ReflectionEnumOption( string name, string comment )
        {
            Name = name;
            Comment = comment;
        }

        /// <summary>
        /// XML shitty serialization - load enumerator option from XML node
        /// </summary>
        /// <param name="enumDef">The parent enumerator that we are loading stuff into</param>
        /// <param name="log">Error/progress tracking interface</param>
        /// <param name="node">The XML node for this enum</param>
        /// <returns>Loaded enum type or NULL if nothing was loaded</returns>
        public static bool LoadFromNode(ReflectionEnum enumDef, IGenericFileErrorReporter log, XmlNode node)
        {
            // the "name" field is expected
            string optionName = Raw.Utils.Attr(node, "name");
            if (optionName == "")
            {
                log.Error( String.Format("Missing attribute 'name' for enum option in enum {0}", enumDef.Name));
                return false;
            }

            // make sure we don't have the same option defined twice
            if (null != enumDef.FindOption(optionName))
            {
                log.Error(String.Format("Option {0} is already defined in enumeration {1}}", optionName, enumDef.Name));
                return false;
            }

            // additional comment
            string comment = Raw.Utils.Attr(node, "comment");

            // add to enum
            ReflectionEnumOption prop = new ReflectionEnumOption(optionName, comment);
            enumDef.Options.Add(prop);

            // no errors
            return true;
        }
    }

    /// <summary>
    /// Type description for an enumeration (enum)
    /// Contains list of selectable user options
    /// </summary>
    public class ReflectionEnum : ReflectionType
    {
        /// <summary>
        /// Was this type fully defined (set true only after Loading occurs)
        /// We need this flag to detect unresolved types
        /// </summary>
        public bool IsDefined { get; private set; }

        /// <summary>
        /// List of options in this enumerator type
        /// </summary>
        public List<ReflectionEnumOption> Options { get; private set; }

        /// <summary>
        /// Initialize the enumerator type
        /// </summary>
        /// <param name="parent">Back pointer to the global reflection data</param>
        /// <param name="name">Name of the type</param>
        internal ReflectionEnum(ReflectionData parent, string name)
            : base( parent, name )
        {
            Options = new List<ReflectionEnumOption>();
            IsDefined = false;
        }

        /// <summary>
        /// Find enumeration option with given system name
        /// </summary>
        /// <param name="name">System name (the saved name) of the enumeration option to find</param>
        /// <returns>Matching enumeration option or NULL if it was not foudn</returns>
        public ReflectionEnumOption FindOption(string name)
        {
            // lame linear search
            foreach (var option in Options)
                if (option.Name == name)
                    return option;

            // nothing found
            return null;
        }

        /// <summary>
        /// XML shitty serialization - load file from XML node
        /// </summary>
        /// <param name="data">The parent reflection data container we are loading stuff into</param>
        /// <param name="log">Error/progress tracking interface</param>
        /// <param name="node">The XML node for this enum</param>
        /// <returns>Loaded enum type or NULL if nothing was loaded</returns>
        public static bool LoadFromNode(ReflectionData data, IGenericFileErrorReporter log, XmlNode node)
        {
            // name is a required attribute
            string enumName = Raw.Utils.Attr(node, "name");
            if (enumName == "")
            {
                log.Error("Missing attribute 'name' for enum");
                return false;
            }

            // create the enum and mark it as defined
            var enumDef = data.GetEnumDef(enumName);
            enumDef.IsDefined = true;
            enumDef.Comment = Raw.Utils.Attr(node, "comment");

            // load options
            int numOptionLoadingErrors = 0;
            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (childNode.Name == "option")
                {
                    if (!ReflectionEnumOption.LoadFromNode(enumDef, log, childNode))
                        numOptionLoadingErrors += 1;                    
                }
            }

            // there were errors loading the options
            if (numOptionLoadingErrors > 0)
            {
                log.Error(String.Format("Found {0} error(s) loading options for enum {1}. The type library is corrupted or not up to date.", numOptionLoadingErrors, enumDef.Name));
                return false;
            }

            // no errors found
            return true;
        }
    }

}
